package cn.infrabase;

import lombok.AccessLevel;
import lombok.Data;
import lombok.Setter;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalUnit;
import java.util.Date;
import java.util.TimeZone;

/**
 * 日期时间。
 *
 * @author Weimin Gao
 * @since 1.0.0
 **/
@Data
@Setter(AccessLevel.PRIVATE)
public class DateTime {

	/**
	 * 日期时间格式化字符串模式，如：2022-12-16T09:55:10.646+08:00
	 */
	public static final String FORMATTER_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";

	/**
	 * 时区，如：Asia/Shanghai
	 */
	private String timezone;

	/**
	 * 时间戳（从1970-01-01T00:00:00Z开始的秒数），如：1670948050766
	 */
	private long instant;

	/**
	 * 时间与格林威治/UTC的时区偏移，如：+08:00
	 */
	private String offset;

	/**
	 * 时间字符格式的值，如：2022-12-14T00:14:10+08:00
	 */
	private String value;

	/**
	 * 构造函数。
	 *
	 * @param datetime 带时区的日期时间对象。
	 */
	private DateTime(ZonedDateTime datetime) {
		setTimezone(datetime.getZone().getId());
		setInstant(datetime.toInstant().toEpochMilli());
		setOffset(datetime.getOffset().getId());
		setValue(format(this));
	}

	/**
	 * 根据当前日期时间并加上或减去指定的时间，返回一个日期时间对象。
	 *
	 * @param amount 加上或减去指定的时间。
	 * @param unit   时间单位。
	 * @return 日期时间对象。
	 */
	public DateTime plus(long amount, TemporalUnit unit) {
		ZonedDateTime datetime = parse2ZonedDateTime(this.value);
		datetime = datetime.plus(amount, unit);
		return new DateTime(datetime);
	}

	/**
	 * 根据给定的日期时间字符串，返回一个日期时间对象。
	 *
	 * @param datetime 日期时间字符串。
	 * @return 日期时间对象。
	 */
	public static DateTime parse(String datetime) {
		return new DateTime(parse2ZonedDateTime(datetime));
	}

	/**
	 * 根据给定的日期对象及系统默认时区，返回一个日期时间对象。
	 *
	 * @param date 日期对象。
	 * @return 日期时间对象。
	 */
	public static DateTime of(Date date) {
		return new DateTime(ZonedDateTime.ofInstant(date.toInstant(), System.defaultTimeZoneId()));
	}

	/**
	 * 返回一个系统默认时区中最大的日期时间对象。
	 *
	 * @return 日期时间对象。
	 */
	public static DateTime max() {
		return new DateTime(ZonedDateTime.of(LocalDateTime.of(9999, 12, 31, 23, 59, 59), System.defaultTimeZoneId()));
	}

	/**
	 * 根据当前系统时间戳及默认时区，返回一个日期时间对象。
	 *
	 * @return 日期时间对象。
	 */
	public static DateTime now() {
		return new DateTime(ZonedDateTime.now(System.defaultTimeZoneId()));
	}

	/**
	 * 根据当前系统时间戳及默认时区并加上或减去指定时间，返回一个日期时间对象。
	 *
	 * @param amount 加上或减去指定的时间。
	 * @param unit   时间单位。
	 * @return 日期时间对象。
	 */
	public static DateTime now(long amount, TemporalUnit unit) {
		return new DateTime(ZonedDateTime.now(System.defaultTimeZoneId()).plus(amount, unit));
	}

	/**
	 * 返回一个带时区偏移量的日期时间字符串，如：2022-12-15T11:18:38+08:00。
	 *
	 * @param datetime 日期时间对象。
	 * @return 日期时间字符串。
	 */
	public static String format(DateTime datetime) {
		SimpleDateFormat formatter = new SimpleDateFormat(FORMATTER_PATTERN);
		formatter.setTimeZone(TimeZone.getTimeZone(datetime.getTimezone()));
		return formatter.format(new Date(datetime.getInstant()));
	}

	/**
	 * 转换为日期对象。
	 *
	 * @return 日期对象。
	 */
	public Date toDate() {
		return new Date(getInstant());
	}

	// -----------------------------------------------------------------------------------------------------------------

	private static ZonedDateTime parse2ZonedDateTime(String datetime) {
		ZonedDateTime ret = ZonedDateTime.parse(datetime, DateTimeFormatter.ofPattern(FORMATTER_PATTERN));
		return ret.toInstant().atZone(System.defaultTimeZoneId());
	}

}
